Skip to content

feat(ui): add the full player's Transcript panel - #478

Merged
chhoumann merged 20 commits into
masterfrom
feat/ui-transcript-panel
Oct 14, 2026
Merged

chhoumann merged 20 commits into
masterfrom
feat/ui-transcript-panel

Conversation

@chhoumann

@chhoumann chhoumann commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

Why

A listener can now read, search and play from an episode's published transcript inside the full player, without writing a note first. The panel follows the line being spoken, plays from any line, and saves the same note the "Transcribe current episode" command writes. Episodes without a published transcript offer the paid transcription in one tap. Refs #348.

What changed

  • TranscriptPanel is a third player panel between Chapters and Up next. It loads the transcript through TRANS1's own findPublishedTranscript and fetchPublishedTranscript and groups cues into sentence-sized lines with the note's groupCues under shorter limits (5 s soft, 30 s hard). There is no second parser and no second grouper.
  • Save as note runs the "Transcribe current episode" path with its paid fallback turned off (transcribeCurrentEpisode({ paid: false })), so the note is the command's note byte for byte and the Free button never sends audio to a provider. "Transcribe with OpenAI" runs the from-audio command. Deepgram users see "Transcribe with Deepgram".
  • fetchPublishedTranscript now redacts the transcript text where it is fetched. It hides the private feed's address, and the transcript's own address when that carries a credential, by the fix(feeds): keep private subscriptions working across devices #428, feat(feeds): protect documented private feed addresses #440 and fix(privacy): keep private feed addresses out of generated notes #454 detection. The note and the panel read that one result, so neither keeps its own sanitizer.
  • The gzip budget in scripts/size.mjs rises from 100,000 to 103,000.

Scope

  • Covers the brief's head (source label, Find, Save as note), lines (speakers, times, highlight, follow, Follow chip, tap to play), untimed paragraphs, loading, error with Retry, and the no-transcript state with the paid button.
  • The paid result does not show in the panel. Plain Whisper returns untimed text, not timed segments, so the existing path opens the saved note, as it does today.
  • It leaves out reading a paid transcript note back into the panel, and an "Open transcript" button once a note exists. See Follow-ups.

Tradeoffs

  • Lines are sentence-sized groups, not raw cues or the note's paragraphs. On the real 2 h 55 min No Agenda 1910 SRT (2,799 cues), raw cues give 2,799 half-sentence rows, the note's paragraphs give 207 rows with a 53 s median, and these limits give 457 rows with a 25 s median, 64% ending on a sentence. Word-level JSON (8,792 cues) gives 354 rows, all ending on a sentence. The prototype's 38:01 and 38:07 cues therefore read as one line.
  • Follow stops on any user scroll, detected by a scroll position the panel did not set, instead of "current line still in view". The in-view rule pulls a reader who scrolls up a little back down on the next line.
  • A failed feed lookup shows Retry, not the paid button, so nobody is offered payment because the network blinked. An episode the cached feed lists without a transcript still shows the paid button.
  • One cached transcript, keyed by episode, so switching panels does not refetch or flash. Save as note fetches again through the command's path, which keeps the service stateless.

Size

The owner approved the panel on 2026-10-14: "This is an incredible feature that's well worth 1.9 KB." It measures +2,576 gzip at the feature commit and +2,691 against the current master after review (99,056 to 101,747). The extra roughly 700 bytes over the estimate, and the budget of 103,000 rather than 102,000, are a coordinator decision to keep Find and the Follow chip and to leave room for fixes in flight.

Step Raw Gzip Delta
master 311fe48 312,126 99,037
e2e journeys 312,126 99,037 0
groupCues limits, transcript.ts, view verbs 312,404 99,128 +91
TranscriptPanel, Player wiring, shared row style 320,694 101,613 +2,485
Budget 103,000; error alert, Save title 320,728 101,619 +6
List name out of the hover tooltip, raw load state 320,832 101,637 +18
Find marks on 1.14's highlight color 320,861 101,643 +6
Merge of master b6e9821 (master alone: 312,164 / 99,052) 320,899 101,646 +3
Review round: Retry on a failed lookup, redaction, free-only Save 321,018 101,718 +72
Round 2: untimed Find hit; merge of master 8603ccf (master alone: 312,251 / 99,056) 321,169 101,728 +10
Comment pass, named checks 321,169 101,729 +1
V-478 round: shared redaction, 44 px phone targets, Find reset test 321,161 101,747 +18

Blast radius

The change adds one panel and touches the player's panel list, Escape order and shared row style. Chapters now shares its row rules with transcript lines, and the view's Escape closes Find before it collapses the player. On phones every panel head control is now at least 44 px tall, including Up next's Edit. renderTranscriptBody no longer redacts, because its input arrives redacted. Outside the panel's files: IPodNotes gains getTranscriptionService(), TranscriptionService.transcribeCurrentEpisode gains a paid option (default unchanged, so the command behaves as before), IconType gains captions (type only), the verify skill gains a transcript screens state, and AGENTS.md states the new budget. No data.json, IAPI or URI change.

Verification

  • Real Obsidian 1.14.4, Linux (isolated vault), with the journeys in tests/e2e/journey-transcript.test.ts. The timed journey plays and sees the current line advance, taps a line and keeps playing from it, finds "PALACE" (marks, Enter moves the hit, Escape closes Find and keeps the player open), then checks that the note Save as note writes is identical to the note the command writes. A second journey shows the paid button, and a third disables it offline. All three fail on master at "Timed out waiting for player panel Transcript" and pass on this branch.
  • npm run test:e2e on a fresh vault passes at the final head, 197 tests in 27 files. npm run lint, format:check, typecheck, build, test (124 files, 2,606 tests), test:tooling (13), docs:build: all pass on the final head. Node 22.23.2.
  • The command's note and Save as note's note are byte-identical to master's for a public feed's VTT, SRT, JSON, HTML and plain text transcript.
  • In Obsidian, a private feed whose transcript quotes its own ?token= address and the feed's address shows neither in the panel or the saved note. On a 390 x 844 phone, Save, the Find field, Follow and Up next's Edit measure 44 px.
  • Performance, measured in that Obsidian under Xvfb at pane width:
    • Real 2 h 55 min SRT (457 lines, 152 KB of text). Cold open is 106-149 ms, including the fetch from a local server, parse and render. Reopen from Up next is 43-59 ms.
    • A line change costs 7.7-8 ms of main-thread work, against 1.7 ms for the same seek with Up next. The current line lands at one third of the list, and no long task occurs across 40 line changes.
    • During steady playback a trace shows the main thread busy 9% of the time, with the largest frame task at 3.6 ms.
    • Very large hostile transcripts render slowly in Reading view #437's hostile 2 MB transcript (1,024 lines of 2,000 characters) opens in 535-714 ms cold and 203-226 ms warm, and follows at 10.4 ms per line. Reading view shows the same text after 5.4-8.6 s. No virtualization.

Mutations (standing order 32)

Each wrong alternative was planted, failed the named test, and was reverted.

Mutant Failing test
Follow by index instead of time lists its lines with their times and speakers, and marks the line playing now
Seek without play a tapped line plays from its start
Save as note renders and writes the note in the panel Save as note runs the transcribe command, which writes the note
The failure text, with a private feed URL, in the panel never shows or logs a private feed's address from the failure
Follow while the current line is in view (geometry) follows the line playing now until the user scrolls, then Follow brings it back
Escape collapses the player before closing Find Find shows the lines with a word, Enter and Shift+Enter step through them, and Escape closes Find before the player
Always "OpenAI" names Deepgram when diarization uses it
A failed load cached, so Retry does not fetch offers Retry, which fetches it again
Lookup failure judged by status error only, missing a stale feed an episode a cached feed lacks, when the feed fails to refresh, fails rather than reporting no transcript
Lookup failure on any feed error, even for a listed episode an episode a cached feed lists without a transcript has none, though the feed failed to refresh
Transcript text shown unredacted timed and untimed transcript text quoting the episode's private feed shows no address
Free path falls back to paid audio saving only the feed's transcript never sends the audio to a provider
Save as note through the paid-capable command path Save as note asks for the feed's transcript without the paid fallback; Transcribe uses the audio
Redaction removed, for a saved private feed's placeholder episode never shows the private feed address a saved private feed's transcript quotes
Untimed Find hit not marked an untimed transcript reads as paragraphs, with no time to play from or follow
Redaction only in the panel a transcript quoting its own credential-bearing address and its private feed's keeps neither in the note
The fetch redacts the feed's address but not the transcript's own transcript text quoting the transcript's own credential-bearing address shows no credential
Find left open across an episode switch another episode's transcript opens with Find closed and every line shown

Screens

320, 820 and 390 in light and dark, each beside the prototype:

Transcript beside the prototype at every width and theme

Light Dark
Sidebar 320
Pane 820
Phone 390
Find
Follow chip, no transcript, offline

Follow, tap to play, scroll away and Follow, then Find "walk" with Enter and Escape, in Obsidian 1.14.4: screen recording (frames).

Docs at release

  • In docs/src/content/docs/podcasts.md, "Play an episode", add a Transcript bullet. It shows the feed's transcript. Select a line to play from it, Find filters the lines, Save as note writes the transcript note, and Follow returns to the line being spoken.
  • In docs/src/content/docs/transcripts.md, add a section on reading a feed's transcript in the player (Feed transcript, Free), and say that without one the panel offers Transcribe with OpenAI (or Deepgram), marked Paid, for audio episodes while online.
  • In docs/src/content/docs/transcripts.md, "Limits and error messages", say that the transcript of a feed that publishes one has timestamps. Pair this line with TRANS1's docs.

Follow-ups

  • The "Transcribe current episode" command still takes the paid path when its feed lookup fails and a key is set. That is TRANS1's behavior, and the panel no longer shares it. Both reviewers suggest the root fix: findPublishedTranscript reports "could not look" apart from "found none", and FeedRepository.load resolves false for an unavailable private address. The panel's own check could then go.
  • The command hides itself for video, while the panel's Save works for a video's published transcript. Moving the audio check into the service's audio branch would let the command match.
  • J-TR-04 says the panel shows an existing transcript note and Save as note opens it. Today the command refuses with a notice when the note exists.
  • Steady playback with the panel open showed 20-35 ms frame gaps under Xvfb's software compositing that the main-thread trace does not explain. One session traced about 4 ms per frame in Chromium's selection-bounds update walking the list. Profile on GPU hardware before reaching for virtualization.

Note

Add full player Transcript panel with search, follow-playback, and note saving

  • Adds a Transcript panel to the full podcast player alongside Chapters and Up next, shown for episodes with or without chapters. It renders timed lines with speakers and clickable timestamps, or untimed paragraphs, follows the playing line, and resumes following via a Follow control after user scroll. TranscriptPanel.svelte
  • Adds case-insensitive Find with highlighted matches, Enter/Shift+Enter match cycling, and Escape closes Find without collapsing the player.
  • Adds Save as note for published transcripts and a paid Transcribe action (Deepgram/OpenAI) for eligible audio episodes; paid transcription is disabled offline and hidden for video episodes or failed feed loads.
  • Moves private-feed credential redaction from rendering into fetchPublishedTranscript, so transcript content and credential-bearing source URLs are sanitized before saving to a note. index.ts
  • transcribeCurrentEpisode gains a paid flag; when false and no published transcript exists, it emits a notice instead of falling back to audio transcription. TranscriptionService.ts
  • Behavioral Change: fetchPublishedTranscript now requires an Episode argument, and ShellServices consumers must implement saveTranscriptNote and transcribeFromAudio. Raises the gzip size budget from 100,000 to 103,000 bytes. size.mjs

Macroscope summarized 46594f3.

The full player has no Transcript panel yet, so all three fail on choosing it.
… transcription verbs

groupCues takes its soft and hard limits, so the panel groups word-level and
half-sentence cues into sentence lines while every note keeps its paragraphs.
transcript.ts loads, groups and caches the current episode's published
transcript. ShellServices gains the two transcription commands, IPodNotes
exposes the transcription service, and SearchField forwards onkeydown.
The player gains a Transcript panel between Chapters and Up next. It shows the
feed's published transcript as sentence lines with times and speakers, marks
and follows the line playing now, plays from a tapped line, finds a word, and
saves the note the Transcribe current episode command writes. A user scroll
stops following until Follow brings the line back. Without a published
transcript it offers the paid transcription from audio. Chapters and transcript
lines share one row style in Panel.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Deploying podnotes with  Cloudflare Pages  Cloudflare Pages

Latest commit: 46594f3
Status: ✅  Deploy successful!
Preview URL: https://7fcda2bd.podnotes.pages.dev
Branch Preview URL: https://feat-ui-transcript-panel.podnotes.pages.dev

View logs

@chhoumann
chhoumann merged commit 8449c62 into master Oct 14, 2026
16 checks passed
@chhoumann
chhoumann deleted the feat/ui-transcript-panel branch October 14, 2026 20:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant